// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jingyuan

#pragma once

#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <nlohmann/json.hpp>

#include "coin2/app/driver.h"
#include "coin2/base/log.h"
#include "coin2/base/math/twma.h"
#include "coin2/exchange/base/feed/subscriber.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/strategy.h"

namespace coin2::strategy::basis_strategy {

using coin::proto::ExchangeType;
using coin::proto::OrderEvent;
using coin::proto::TradeSide;
using coin2::app::Driver;
using coin2::base::math::TimeWeightedMovingAverage;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::market::MarketExchangeApi;
using coin2::exchange::base::order::IOrderSubsystem;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::symbology::ProductEncyclopedia;
using coin2::strategy::IStrategy;
using coin2::strategy::PassiveExecutorConfig;
using coin2::strategy::order_executor::PassiveOrderExecutor;
using std::experimental::filesystem::path;

std::string GetProductKey(const IProduct* product);

class BasisStrategy;

class BasisSubStrategy : public IStrategy {
 public:
  BasisSubStrategy(
      BasisStrategy* master_strategy,
      const nlohmann::json& prod_dict,
      const PassiveExecutorConfig& exe_config,
      std::unique_ptr<IProduct> product,
      int64_t timestamp);

  void Init(Driver* driver) override;
  void onBookFeed(const FeedUpdate& upd) override;
  void onAccountOrder(const OrderUpdate& upd) override;
  void onAccountInfo(const OrderUpdate& upd) override;
  void onOrderLog(const OrderGatewayLog& log) override;

  void FireAggressiveOrderOnNextBook();
  double GetPosition() const; // returns the expouse in base ccy
  double GetMaxPosition() const { return previous_max_; }
  double GetMinPosition() const { return previous_min_; }
  PassiveOrderExecutor* GetMutableExecutor() { return order_executor_.get(); }
  bool SyncPosition();
  StrategyManagerConfig& reporter_config() { return reporter_config_; }
  const IProduct& product() { return *product_; }
  IOrderSubsystem* order_system() { return driver_->order()->GetUniqueSystem(mea()); }
  void LogCurrentStatus() const;
  void ManageTotalRisk(int64_t timestamp);

  TradeSide last_trade_side_ {TradeSide::TRADE_NO_SIDE};

 private:
  double max_position_; // hard position limit per venue
  double min_position_;
  double position_;
  double simulation_starting_position_;
  bool fire_aggressive_order_ {false};
  double max_lean_bps_;
  double previous_min_; // for logging only
  double previous_max_; // for logging only
  double price_push_bps_;
  double price_pull_bps_;
  double lot_size_; // lot size in base ccy exposure
  double contract_value_;
  bool is_spot_;
  MarketExchangeApi mea() { return MarketExchangeApi::FromString(exe_config_.mea()); }
  BasisStrategy* master_strategy_;
  ExchangeType exchange_;
  std::string strategy_key_;
  Driver* driver_;
  PassiveExecutorConfig exe_config_;
  StrategyManagerConfig reporter_config_;
  std::unique_ptr<IProduct> product_;
  std::unique_ptr<PassiveOrderExecutor> order_executor_;
  std::unique_ptr<StrategyReporter> strat_reporter_;
  int64_t timestamp_;
};

class BasisStrategy : public IStrategy {
 public:
  BasisStrategy(const path& config_json, int64_t timestamp);

  void Init(Driver* driver) override;

  void onBookFeed(const FeedUpdate& upd) override;
  void onTradeFeed(const FeedUpdate& upd) override;
  void onAccountOrder(const OrderUpdate& upd) override;
  void onAccountInfo(const OrderUpdate& upd) override;
  void onOrderLog(const OrderGatewayLog& log) override;
  void onCleanup(Driver* driver) override;

  const std::string& otherStrategy(const std::string& key) {
    return key == strategy_keys[0] ? strategy_keys[1] : strategy_keys[0];
  }

  // estimate the fair value from the past MA, returns the bid and ask
  std::optional<std::pair<double, double>> fair_value(const std::string& key);
  void LogCurrentStatus();

  ProductEncyclopedia product_cache;
  double GetPosition() { return position_; }
  void SyncPositionAndRisk(int64_t timestamp);
  double GetMaxPosition() { return max_position_; }
  StrategyReporter* GetStrategyReporter(const StrategyManagerConfig& config);
  std::unordered_map<std::string, double>& GetReserve(const StrategyManagerConfig& config);
  double GetLastMid(const std::string& key) { return last_mids[key]; }

 private:
  // twma = MA(exchange0.mid-exchange1.mid)
  TimeWeightedMovingAverage twma_;
  Driver* driver_;
  double edge_;
  bool enable_aggressive_order_;
  int64_t last_aggressive_order_timestamp_ {0};
  int64_t aggressive_order_cooldown_;
  int64_t last_log_ts_ {0};
  double max_lean_bps_;
  double max_position_; // total position limit, strategy should approach a 0 net position
  double position_;
  double lot_size_;
  std::map<std::string, double> last_mids;
  std::map<std::string, std::unique_ptr<BasisSubStrategy>> strategies;
  std::map<std::string, std::unordered_map<std::string, double>> reserves_;
  std::map<std::string, std::unique_ptr<StrategyReporter>> strat_reporters_;
  std::vector<std::string> strategy_keys;
};
}  // namespace coin2::strategy::basis_strategy
